package fuse.LaCollaFS;

import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.rmi.RemoteException;
import java.util.logging.*;


import LaColla.Api.Api;
import LaColla.core.data.TimestampSummary;
import LaColla.core.util.Debug;
import fuse.FuseException;
import fuse.FuseMount;
import fuse.FuseStatfs;
import fuse.LaCollaFS.Api.ApiImpl;
import fuse.LaCollaFS.Api.ApiServer;
import fuse.LaCollaFS.Metadades.*;
import fuse.Filesystem1;
import fuse.FuseDirEnt;
import fuse.FuseStat;



public class LaCollaFS implements Filesystem1 {

	private String defpath = "/tmp/lacollafs";

	private Organitzador org;

	private static MetaDades dades;

	private static final int RMIport = 11099;

	private static String appIP;

	private static int apiPort = 19000;

	private static int appPort = 4321;

	private static String username = "admin";

	private static String passwd = "admin";

	private static String groupId = "hola";

	private Api api;

	private ApiServer aserv;

	private String memberId;

	private String apiid;

	 private static final Logger log= Logger.getLogger("LaCollaFS.class");
	/**
	 * Constructor de LaCollaFS inicialitza el api de lacolla inicialitza les
	 * metadades crea el fitxer arrel.
	 * 
	 * TODO veure com fer les reconexións o una nova conexió a l'api per
	 * compartir el dir.xml
	 * 
	 * @param user
	 *            TODO
	 * @throws IOException
	 *             TODO
	 */
	public LaCollaFS(String user) throws IOException {
		

		 FileHandler fh = new FileHandler("/tmp/LaCollaFS.log");
		 fh.setFormatter(new SimpleFormatter());
		 log.addHandler(fh);
		 
		 log.setLevel(Level.ALL);
		 
		/// log.info("Un mensaje cualquiera...");
		//log.info("hoolaaaaaaaaaaaaaaaaaaaaaaaaa");

		try {
			appIP = InetAddress.getLocalHost().getCanonicalHostName();
		} catch (UnknownHostException e1) {

			e1.printStackTrace();
		}
		// Debug.say("hola LaCollaFS", appIP);
		log.info("LaCollaFS : Host= " + appIP + " appPort=" + appPort
				+ " LaCOLLAport= " + apiPort);

		aserv = new ApiServer(appIP, appPort);
		// p("Api Server creat..." + aserv);

		api = aserv.resolveApi(apiPort);
		TimestampSummary s;
		s = aserv.readSummary();
		// p("tss:"+s);

		String gapaIP = appIP;
		int gapaPort = 2001;
		apiid = api.login(groupId, username, passwd, gapaIP, gapaPort, appIP,
				appPort, aserv.readSummary());
		// en aquest punt encara pot no haber arribat el
		// newConnectedMemberEvent, s'hauria de bloquejar..
		try {
			Thread.sleep(1000);
		} catch (Exception e) {
		} // esperem rebre l'event
		memberId = ((ApiImpl) aserv.getASAI()).getMemberId();
		log.info("Membre conectat, el meu id és : " + memberId);
		log.info("Numero de nodes presents: "+aserv.getNumMembers());
		dades = new MetaDades(defpath, api, aserv, apiid);
		org = new Organitzador(api, aserv, apiid);

	}

	/**
	 * Canviar els permisos d'un node de les metadades Quan acaba desa els
	 * canvis a lacolla
	 * 
	 * @param path
	 *            path del node a modificar
	 * @param mode
	 *            integer amb els nous permisos
	 */
	public void chmod(String path, int mode) throws FuseException {
		log.info("=====FUSE OPERATION: CHMOD, params : path = " + path + " , mode = "
				+ mode + "=====");
		FuseStat estat;
		int id = org.CanviarStat(path);
		if (id == -1) {
			throw new FuseException("Consens no aconseguit no arribat!!!!")
					.initErrno(FuseException.EBUSY);

		} else {
			estat = dades.ObtenirStat(path);
			estat.mode = mode;
			dades.CanviarStat(path, estat);
			org.Fet(id);
		}

	}

	/**
	 * Canvia el propietari/grup del objecte a les metadades
	 * 
	 * @param path
	 *            path del objecte a modificar
	 * @param uid
	 *            identificador del nou propietari
	 * @param gid
	 *            identificador del grup del nou propietari
	 */
	public void chown(String path, int uid, int gid) throws FuseException {
		log.info("=====FUSE OPERATION: CHOWN, params : path = " + path + ", UID = "
				+ uid + ", GID = " + gid + "" + " =====");
		FuseStat estat;
		int id = org.CanviarStat(path);
		if (id == -1) {
			throw new FuseException("Consens no aconseguit no arribat!!!!")
					.initErrno(FuseException.EBUSY);

		} else {
			estat = dades.ObtenirStat(path);
			// si uid val -1 es que volem fer un canvi del gid, quan es un canvi
			// en
			// el uid el que val -1 es el uid
			if (uid == -1) {
				estat.gid = gid;
			} else if (gid == -1) {
				estat.uid = uid;
			}

			dades.CanviarStat(path, estat);
			org.Fet(id);
		}
	}

	/**
	 * Retorna el estat del node en questió
	 * 
	 * @param path
	 *            path del node a consultar el stat
	 */
	public FuseStat getattr(String path) throws FuseException {
		log.info("=====FUSE OPERATION: GETATTR, params : path = " + path + "=====");
		org.ObtenirStat(path);
		return dades.ObtenirStat(path);
	}

	/**
	 * Retorna el contingut del directori
	 * 
	 * @param path
	 *            path del directori del que volem saber els fills
	 */
	public FuseDirEnt[] getdir(String path) {
		log.info("=====FUSE OPERATION: GETDIR, params : path = " + path + "=====");
		Directori node;
		FuseDirEnt[] dir;

		int id = org.LlegirDir(path);
		if (id == -1) {
			try {
				throw new FuseException("Consens no aconseguit no arribat!!!!")
						.initErrno(FuseException.EBUSY);
			} catch (FuseException e) {
				dir = null;
				e.printStackTrace();
			}

		} else {
			node = (Directori) dades.ObtenirNode(path);
			dir = node.Llistarfills();

			// p("nem a veure quan val el lenght del dir..."+ dir.length);
			org.Fet(id);
		}
		return dir;

	}

	/**
	 * Crea un nou dorectori
	 * 
	 * @param path
	 *            path del nou directori, nom inclos
	 * @param mode
	 *            mode (permisos) amb que s'ha de crear el nou directori
	 */
	public void mkdir(String path, int mode) throws FuseException {
		log.info("=====FUSE OPERATION: MKDIR, params : path = " + path + ", mode ="
				+ mode + "=====");
		Directori node;
		Directori nou;

		String pathNode;
		String pathNom;

		pathNode = dades.ObtenirDir(path);
		pathNom = dades.ObtenirNom(path);

		int id = org.ModificarDir(dades.ObtenirDir(path));
		if (id == -1) {
			throw new FuseException("Consens no aconseguit no arribat!!!!")
					.initErrno(FuseException.EBUSY);

		} else {
			node = (Directori) dades.ObtenirNode(pathNode);
			nou = new Directori(pathNom);
		//	FuseStat stat = nou.getStat();
		//	stat.mode = mode;
		//	nou.setStat(stat);
			node.AfegirFill(nou);
			dades.DesaTree();

			org.Fet(id);
		}
	}

	/**
	 * Elimina un directori
	 * 
	 */
	public void rmdir(String path) throws FuseException {
		log.info("=====FUSE OPERATION: RMDIR, params : path = " + path + "=====");
		// log.info("rmdir!!!!!!!!!");
		Directori node;

		// demanem permis per modificar el pare i dp per modificar el node en
		// questio, aixi ens asegurem de que ningú modifica el node ni el
		// directori pare.
		int id = org.ModificarDir(dades.ObtenirDir(path));
		int id2 = org.ModificarDir(path);
		if ((id == -1) || (id2 == -1)) {
			throw new FuseException("Consens no aconseguit no arribat!!!!")
					.initErrno(FuseException.EBUSY);

		} else {
			node = (Directori) dades.ObtenirNode(path);
			if (node.Buit()) {
				node.getPare().EliminarFill(node.getNom());
				// dades.EliminarNode(path);
				dades.DesaTree();
			} else
				throw new FuseException("Directori no buit!!!")
						.initErrno(FuseException.ENOTEMPTY);

			org.Fet(id);
			org.Fet(id2);
		}
	}

	/**
	 * Crea un nou fitxer
	 */
	public void mknod(String path, int mode, int rdev) throws FuseException {
		log.info("=====FUSE OPERATION: MKNOD, params : path = " + path + " , mode = "
				+ mode + " , rdev = " + rdev + "=====");
		String pathNode;
		String pathNom;
		pathNode = dades.ObtenirDir(path);
		pathNom = dades.ObtenirNom(path);
		System.out.println("directori del fitxer "+pathNode);
		System.out.println("Nom del fitxer "+pathNom);

		Directori node;
		Fitxer nou;
		int id = org.ModificarDir(dades.ObtenirDir(path));
		if (id == -1) {
			throw new FuseException("Consens no aconseguit no arribat!!!!")
					.initErrno(FuseException.EBUSY);

		} else {
			node = (Directori) dades.ObtenirNode(pathNode);
			nou = new Fitxer(pathNom);
			FuseStat estat;
			estat = nou.getStat();
			estat.mode = mode;
			nou.setStat(estat);
			node.AfegirFill(nou);
			// p("fitxer creat, ara anem a guardar el xml...");
			dades.DesaTree();

			org.Fet(id);
		}
	}

	/**
	 * Comproba si tenim permisos per obrir el fitxer i el crea en local
	 * 
	 */
	public void open(String path, int flags) {
		log.info("=====FUSE OPERATION: OPEN, params : path = " + path + ", flags= "
				+ flags + "=====");

		Fitxer node;
		int id = 0;

		node = (Fitxer) dades.ObtenirNode(path);
		log.info("creant el directori desti...");
		File direct = new File(defpath + dades.ObtenirDir(path));
		direct.mkdirs();
		log.info("dir creat!!!!" + direct.getAbsolutePath());
		if (node == null) {
			log.info("El fitxer no  existeix!!!");
			try {
				throw new FuseException("El fitxer no  existeix!!!")
						.initErrno(FuseException.EEXIST);
			} catch (FuseException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} else {

			boolean l = false;
			boolean e = false;
			flags = flags - 32768;

			if (flags == O_RDONLY) {

				l = true;
				id = org.LlegirFitxer(path);

			}
			if (flags == O_RDWR) {
				l = true;
				e = true;
				id = org.EscriureFitxer(path);
			}
			if (flags == O_WRONLY) {
				e = true;
				id = org.EscriureFitxer(path);
			}
		//	p("l/e : " + l + e);
			if (id == -1) {
				try {
					throw new FuseException(
							"Consens no aconseguit no arribat!!!!")
							.initErrno(FuseException.EBUSY);
				} catch (FuseException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}

			} else {
				dades.ObrirFitxer(path, l, e);
			}
		}

	}

	/**
	 * llegeix un fitxer
	 */
	public void read(String path, ByteBuffer buf, long offset)
			throws FuseException {
		log.info("=====FUSE OPERATION: READ, params : path = " + path + "=====");
		// int id = org.LlegirFitxer(path);

		dades.Llegirfitxer(path, offset, buf);

		// org.Fet(id);
	}

	/**
	 * Escriu canvis en un fitxer i ho puja a la colla quan acaba.
	 */
	public void write(String path, ByteBuffer buf, long offset) {
		log.info("=====FUSE OPERATION: WRITE, params : path = " + path + " =====");
		// int id = org.EscriureFitxer(path);

		dades.Escriurefitxer(path, buf, offset);

		// org.Fet(id);

	}

	/**
	 * Puja els canvis d'un fitxer a lacolla i l'allibera TODO
	 */
	public void release(String path, int flags) {
		log.info("=====FUSE OPERATION: RELEASE, params : path = " + path + " flags : "+flags+"=====");

		dades.TancarFitxer(path);
		flags = flags - 32768;
		if (flags == O_RDWR || flags == O_WRONLY) {

			// int id = org.DesaFitxer(path);
			dades.PujaFitxer(path);

			// org.Fet(id);
		}
		org.FitxerDesat(path);

	}

	/**
	 * Canvia les dates d'un Objecte a les metadades.
	 */
	public void utime(String path, int atime, int mtime) throws FuseException {
		log.info("=====FUSE OPERATION: UTIME, params : path = " + path + "=====");
		int id = org.EscriureFitxer(path);
		if (id == -1) {
			throw new FuseException("Consens no aconseguit no arribat!!!!")
					.initErrno(FuseException.EBUSY);

		} else {
			Fitxer fit;
			FuseStat estat;

			fit = (Fitxer) dades.ObtenirNode(path);
			estat = fit.getStat();
			estat.atime = atime;
			estat.mtime = mtime;
			fit.setStat(estat);
			dades.DesaTree();
			org.Fet(id);
		}
	}

	/**
	 * Crea un HardLink. Per fer-ho fa una copia del fitxer en la nova ubicació.
	 * Per a les nostres metadades, aquest link serà com si fós un fitxer més.
	 * 
	 * @param from
	 *            node origen
	 * @param to
	 *            node destí
	 */
	public void link(String from, String to) throws FuseException {
		log.info("=====FUSE OPERATION: LINK, params : from = " + from + ", to = " + to
				+ "=====");
		// hard link!!!
		Fitxer nodefrom;
		Directori dirto;

		int id = org.CrearEnllas(from, to);
		if (id == -1) {
			throw new FuseException("Consens no aconseguit no arribat!!!!")
					.initErrno(FuseException.EBUSY);

		} else {
			dirto = (Directori) dades.ObtenirNode(dades.ObtenirDir(to));
			log.info("dir to es " + dirto.getNom());
			Fitxer fit = new Fitxer(dades.ObtenirNom(to));
			nodefrom = (Fitxer) dades.ObtenirNode(from);
			log.info("fitxer from te de nom " + nodefrom.getNom());
			fit = nodefrom.copy();
			dirto.AfegirFill(fit);
			dades.DesaTree();
			org.Fet(id);
		}

	}

	/**
	 * Crea un enllaç simbolic amb l'Objecte Enllas de les metadades
	 */
	public void symlink(String from, String to) throws FuseException {
		log.info("=====FUSE OPERATION: SYMLINK, params : from = " + from + ", to = "
				+ to + "=====");
		Node nodefrom;
		Directori nodeto;

		int id = org.CrearEnllas(from, to);
		if (id == -1) {
			throw new FuseException("Consens no aconseguit no arribat!!!!")
					.initErrno(FuseException.EBUSY);

		} else {
			nodeto = (Directori) dades.ObtenirNode(dades.ObtenirDir(to));

			Enllas en = new Enllas(dades.ObtenirNom(to));
			//nodefrom = dades.ObtenirNode(from);
	//		p("node from enlacollafs" +from);
			en.setOriginal(from);

			nodeto.AfegirFill(en);
		//	p("enllas creat!!!");
			dades.DesaTree();
			org.Fet(id);
		}
	}

	/**
	 * Llegeix un link sinbolic TODO
	 * 
	 */
	public String readlink(String path) throws FuseException {
		log.info("=====FUSE OPERATION: READLINK, params : path = " + path + "=====");
		int id = org.LlegirEnllas(path);
		if (id == -1) {
			throw new FuseException("Consens no aconseguit no arribat!!!!")
					.initErrno(FuseException.EBUSY);

		} else {
			Enllas en;
			Node node;
log.info("arribo?");
			en = (Enllas) dades.ObtenirNode(path);
			log.info("path original "+ en.getOriginal());
			node = dades.ObtenirNode(en.getOriginal());

			org.Fet(id);
			return node.ObtenirPath();
		}
	}

	/**
	 * Canvia el nom d'un Objecte TODO fer-ho tb per fitxers!!!!
	 */
	public void rename(String from, String to) throws FuseException {
		log.info("=====FUSE OPERATION: RENAME, params : from = " + from + ", to = "
				+ to + "=====");
		// demano permisos

		int id = org.MoureNode(from, to);
		log.info("id retornat per l'organitzador " + id);
		if (id == -1) {
			throw new FuseException("Consens no aconseguit no arribat!!!!")
					.initErrno(FuseException.EBUSY);
		} else {
			dades.moureNode(from, to);
			org.Fet(id);
		}
	}

	/**
	 * Retorna el Estat del FileSystem
	 */
	public FuseStatfs statfs() throws FuseException {
		log.info("=====FUSE OPERATION: STATFS, =====");
		//p("statfs!!!!!!!");

		return dades.getStat();
	}

	/**
	 * Canvia el tamany d'un fitxer
	 */
	public void truncate(String path, long size) throws FuseException {
		log.info("=====FUSE OPERATION: TRUNCATE, params : path = " + path + " size = "+size+" =====");
		int id = org.EscriureFitxer(path);
		if (id == -1) {
			throw new FuseException("Consens no aconseguit no arribat!!!!")
					.initErrno(FuseException.EBUSY);

		} else {
			Fitxer fit;
			FuseStat estat;

			fit = (Fitxer) dades.ObtenirNode(path);
			estat = fit.getStat();
			estat.size = size;
			// modificar dades!!!!
			fit.setStat(estat);
			dades.DesaTree();
			org.Fet(id);
		}
	}

	/**
	 * Elimina un fitxer de les metadades TODO control de versions!!!
	 */
	public void unlink(String path) throws FuseException {
		log.info("=====FUSE OPERATION: UNLINK, params : path = " + path + "=====");
		int id = org.EliminarFitxer(path);
		if (id == -1) {
			throw new FuseException("Consens no aconseguit no arribat!!!!")
			.initErrno(FuseException.EBUSY);
			
		}else{
		Node fit = dades.ObtenirNode(path);
		fit.getPare().EliminarFill(fit.getNom());

		dades.DesaTree();

		org.Fet(id);
		}

	}

	/**
	 * Imprimeix per pantalla
	 * 
	 * @param o
	 */
	/*public void p(Object o) {
		//System.out.println("LaCollaFS "+o);
		log.info("LaCollaFS: "+o);
		
	}*/

	/**
	 * Main de la clase, crida al fusemount.
	 * 
	 * @param args
	 *            el primer parametre li passem un fitxer de text amb la
	 *            configuració de lacolla especifica en el segon parametre li
	 *            passem el punt de montatje
	 * 
	 */
	public static void main(String[] args) {
		if (args.length < 1) {
			System.out.println("Must specify params file");
			System.exit(-1);
		}

		String fuseArgs[] = new String[args.length - 1];
		System.arraycopy(args, 0, fuseArgs, 0, fuseArgs.length);

		try {
			// System.out.println(" argument de lacollafs "
			// + args[args.length - 1]);

			LaCollaFS lacollafs = new LaCollaFS(fuseArgs[2]);
			FuseMount.mount(fuseArgs, lacollafs);
			lacollafs.close();
			System.out.println("Fitxer desmontat!!!!!! i penjat....");

		} catch (Exception e) {
			e.printStackTrace();
		}
		// System.out.println("Fitxer desmontat!!!!!! i penjat....");

	}

	public void close() {
		try {
			String memberId;
			memberId = ((ApiImpl) aserv.getASAI()).getMemberId();
			api.logout(groupId, memberId, apiid);
			aserv.unpublish();
			aserv.close();
			
			// aserv.unpublish();
		} catch (RemoteException e) {
			log.warning("error al suicidar les metadades");
			e.printStackTrace();
		}

	}

}
